﻿using Microsoft.AspNet.Hosting;
using Microsoft.AspNet.Mvc;
using Microsoft.Extensions.Caching.Distributed;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.PlatformAbstractions;
using Sino.CapacityCloud.Infrastructure.Events;
using Sino.CapacityCloud.Repositories.Models;
using Sino.CapacityCloud.Services;
using Sino.Tms.Primitives;
using Sino.TMSystem;
using Sino.WebApi.Framework.Responses;
using System;
using System.Linq;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Data.Entity;
using AutoMapper;
using Sino.WebApi.Framework.Protocol;
using Sino.WebApi.Framework.Primitives;
using Sino.CapacityCloud.Repositories.EventData;
using Sino.WebApi.Framework.Core.Filters;

namespace Sino.CapacityCloud.WebApi.ApiControllers
{

    public class CarController
        : ApiController<CarController>
    {

        private readonly ITmsServiceBus _tmsService;
        private readonly IEventBus _eventBus;

        public CarController(
            IHostingEnvironment env,
            IApplicationEnvironment applicationEnvironment,
            IDistributedCache cache,
            ITokenGenertationService tokenGenertationService,
            IEventBus eventBus,
            ApplicationDbContext dbContext,
            ApplicationUserManager userManager,
            ApplicationRoleManager roleManager,
            ApplicationSignInManager signInManager,
            ILoggerFactory loggerFactory,
            ITmsServiceBus tmsService)
            : base(env, applicationEnvironment, cache, tokenGenertationService, dbContext, userManager, roleManager, signInManager, loggerFactory)
        {
            this._eventBus = eventBus;
            this._tmsService = tmsService;
        }

        [SupressUserLogin]
        public async Task<JsonResult> GetCars(string ids, string orderby, int asc, string carcode, VehicleType cartype, CarLength carlength, string inputperson, string driver, string driverphone, CarriageType carriercategory, string routeKeyword, int isenabled, DateTime? startTime, DateTime? endTime, int skip = 0, int count = 10)
        {
            try
            {
                var id_list = ids?.Split(new char[] { ',' }, StringSplitOptions.RemoveEmptyEntries);

                IQueryable<Car> list = this.DataContext.Cars
                    .AsNoTracking()
                    .Include(x => x.AddingCommit)
                    .Include(x => x.Users)
                    .ThenInclude(x => x.CommonTransportLinkages)
                    .ThenInclude(x => x.TransportLinkage)
                    .Include(x => x.Users)
                    .ThenInclude(x => x.BankCards)
                    .ThenInclude(x => x.Bank);

                if (ids != null && id_list?.Count() > 0)
                {
                    list = list.Where(x => id_list.Contains(x.CarId));
                }
                if (!string.IsNullOrWhiteSpace(carcode))
                {
                    list = list.Where(x => string.Equals(x.Code, carcode, StringComparison.InvariantCultureIgnoreCase));
                }
                if (cartype != VehicleType.None)
                {
                    list = list.Where(x => x.Type == cartype);
                }
                if (carlength != CarLength.None)
                {
                    list = list.Where(x => x.Length == carlength);
                }
                if (carriercategory != CarriageType.None)
                {
                    list = list.Where(x => x.CarriageType == carriercategory);
                }
                switch (isenabled)
                {
                    case 2:
                        list = list.Where(x => x.IsEnabled == false);
                        break;
                    case 1:
                    case 0:
                    default:
                        list = list.Where(x => x.IsEnabled == true);
                        break;
                }
                if (startTime != null)
                {
                    list = list.Where(x => x.Date >= startTime);
                }
                if (endTime != null)
                {
                    list = list.Where(x => x.Date < endTime);
                }
                if (!string.IsNullOrWhiteSpace(inputperson))
                {
                    list = list.Where(x => x.AddingCommit.Committer == inputperson);
                }
                if (!string.IsNullOrWhiteSpace(driverphone))
                {
                    list = list.Where(x => x.Users.Any(y => y.PhoneNumber == driver));
                }
                if (!string.IsNullOrWhiteSpace(driver))
                {
                    list = list.Where(x => x.Users.Any(y => y.RealName == driver));
                }
                if (!string.IsNullOrWhiteSpace(routeKeyword))
                {
                    list = list.Where(x => x.Users
                        .Any(y => y.CommonTransportLinkages
                            .Any(z => z.TransportLinkage.StartingPlaceName.Contains(routeKeyword) ||
                                z.TransportLinkage.DestinationPlaceName.Contains(routeKeyword))));
                }

                var result = list.ToList()
                    .Select(x => Mapper.Map(x, new CarDetails()));

                if (!string.IsNullOrWhiteSpace(orderby))
                {
                    result = result.AsQueryable().OrderBy(orderby, asc != 1);
                }

                var total = result.Count();

                if (total > 0)
                {
                    var response = new CarListResponse()
                    {
                        List = result.Skip(skip).Take(count).ToList(),
                        Total = total
                    };
                    return await this.PackageResultAsync(response);
                }
                return await this.PackageResultAsync(new CarListResponse());
            }
            catch (Exception ex)
            {
                this._logger.LogError($"Failed to find cars that match the filters you given because: {ex.Message + Environment.NewLine + ex.StackTrace}");
                return await this.PackageResultAsync<CarListResponse>(ex);
            }
        }

        [SupressUserLogin]
        public async Task<JsonResult> GetCarDetails(string id)
        {
            try
            {
                var car = this.DataContext.Cars
                    .AsNoTracking()
                    .Include(x => x.Users)
                    .ThenInclude(x => x.BankCards)
                    .ThenInclude(x => x.Bank)
                    .Include(x => x.Users)
                    .ThenInclude(x => x.CommonTransportLinkages)
                    .ThenInclude(x => x.TransportLinkage)
                    .FirstOrDefault(x => x.CarId == id);

                if (car != null)
                {
                    var response = new ValueResponse<CarDetails>()
                    {
                        Value = Mapper.Map(car, new CarDetails())
                    };
                    return await this.PackageResultAsync(response);
                }
                throw new ArgumentOutOfRangeException("The given car id does not exist.");
            }
            catch (Exception ex)
            {
                this._logger.LogError($"Failed to find a car that matces the id you given because: {ex.Message + Environment.NewLine + ex.StackTrace}");
                return await this.PackageResultAsync<ValueResponse<CarDetails>>(ex);
            }
        }

        [SupressUserLogin]
        public async Task<JsonResult> GetCarDetailsByUserId(string id)
        {
            try
            {
                var user = this.DataContext.Users
                    .AsNoTracking()
                    .FirstOrDefault(x => x.Id == id);
                if (user != null && user.CarId != null)
                {
                    user.Car = this.DataContext.Cars
                        .AsNoTracking()
                        .Include(x => x.AddingCommit)
                        .Include(x => x.Users)
                            .ThenInclude(x => x.Car)
                                .ThenInclude(x => x.AddingCommit)
                        .Include(x => x.Users)
                            .ThenInclude(x => x.CommonTransportLinkages)
                                .ThenInclude(x => x.TransportLinkage)
                        .Include(x => x.Users)
                            .ThenInclude(x => x.BankCards)
                                .ThenInclude(x => x.Bank)
                        .FirstOrDefault(x => x.CarId == user.CarId);
                    if (user.Car != null)
                    {
                        var response = new ValueResponse<CarDetails>()
                        {
                            Value = Mapper.Map(user.Car, new CarDetails())
                        };
                        return await this.PackageResultAsync(response);
                    }
                }
                throw new ArgumentOutOfRangeException("The given user id does not exist.");
            }
            catch (Exception ex)
            {
                this._logger.LogError($"Failed to find a car that matces the id you given because: {ex.Message + Environment.NewLine + ex.StackTrace}");
                return await this.PackageResultAsync<ValueResponse<CarDetails>>(ex);
            }
        }

        [SupressUserLogin]
        public async Task<JsonResult> CommitNewCar(
            string carCode,
            VehicleType carType,
            CarLength carLength,
            string inputPerson,
            string driver,
            string driverPhone,
            CarriageType carrierCategory,
            double bearing,
            string carSource,
            string address)
        {
            try
            {
                var commit = this.DataContext.CarAddingModerationCommits
                    .FirstOrDefault(x => x.CarCode == carCode);
                if (commit != null)
                {
                    return await this.PackageResultAsync(new VoidResponse(), new ErrorBase()
                    {
                        Code = -1,
                        Message = $"The requesting car code {carCode} is existed in the commition queue."
                    });
                }
                else
                {
                    commit = new CarAddingModerationCommit()
                    {
                        Address = address,
                        AssignmentOfLoad = bearing,
                        CarCode = carCode,
                        CarriageType = carrierCategory,
                        Committer = inputPerson,
                        Driver = driver,
                        Length = carLength,
                        PhoneNumber = driverPhone,
                        Source = carSource,
                        Type = carType,
                        ApplicationTime = DateTime.Now
                    };
                    this.DataContext.CarAddingModerationCommits.Add(commit);
                    this.DataContext.SaveChanges();
                    this._eventBus.Post(Mapper.Map(commit, new CarAddApplicationEventData() { }), TimeSpan.Zero);
                    return await this.PackageResultAsync(new VoidResponse());
                }
            }
            catch (Exception ex)
            {
                this._logger.LogError($"Failed to add a new car addition commition because: {ex.Message + Environment.NewLine + ex.StackTrace}");
                return await this.PackageResultAsync<VoidResponse>(ex);
            }
        }

        [SupressUserLogin]
        public async Task<JsonResult> ModifyCarLocationState(string carId, int state)
        {
            try
            {
                var car = this.DataContext.Cars.FirstOrDefault(x => x.CarId == carId);
                if (car != null)
                {
                    switch (state)
                    {
                        case 1:
                            car.CanTargeted = true;
                            break;
                        case 2:
                        case 0:
                        default:
                            car.CanTargeted = false;
                            break;
                    }
                    this.DataContext.SaveChanges();
                    return await this.PackageResultAsync(new VoidResponse());
                }
                throw new ArgumentOutOfRangeException("The given car id does not exist.");
            }
            catch (Exception ex)
            {
                this._logger.LogError($"Failed to find a car that matces the id you given because: {ex.Message + Environment.NewLine + ex.StackTrace}");
                return await this.PackageResultAsync<ValueResponse<CarDetails>>(ex);
            }
        }

    }

}